package highlight;

import javax.swing.text.*;
import javax.swing.undo.UndoManager;
import java.util.*;
import java.awt.*;

/**
 * Document stylisé pour la coloration syntaxique dans un JTextComponent. La coloration est automatique. 
 * 
 * @author			Christos Kornaros, Grégory Moinat, Nicolas Muller
 * @version			1.0, 02.04.2008
 * Fichier :		CodeDocument.java
 * Remarque(s) :	-
 * Compilateur : 	jdk1.6.0_02
*/ 
public class CodeDocument extends DefaultStyledDocument{


	static final long serialVersionUID = 1;

	//Liste des attributs pas défaut
	/**
	 * Couleur par défaut des mots clés
	 */
	public final static Color defaultKeywordColor = new Color(0xa44a87);
	
	/**
	 * Couleur par défaut des chaines de caractère
	 */
	public final static Color defaultStringColor = Color.red; 
	
	/**
	 * Couleur par défaut des chiffres
	 */
	public final static Color defaultNumberColor = Color.blue;
	
	/**
	 * Couleur par défaut des caractères
	 */
	public final static Color defaultCharacterColor = Color.orange;
	
	/**
	 * Couleur par défaut des commentaires
	 */
	public final static Color defaultCommentsColor = new Color(0x65bc90);
	
	/**
	 * Couleur par défaut des instruction préprocesseur
	 */
	public final static Color defaultPreprocessColor = new Color(0xa17bb9);
	
	/**
	 * Etet de remplacement par defautr des tabs
	 */
	public final static Boolean defaultReplaceTab = false;
	
	/**
	 *  Taille par défaut des tabulations
	 */
	public final static Integer defaultTabSize = 3;
	
	
	//Liste des attributs de coloration
	private Style keywordAttributes = addStyle("keywordAttributes", null);
	private Style stringAttributes = addStyle("stringAttributes", null);
	private Style normalAttributes = addStyle("normalAttributes", null);
	private Style numberAttributes = addStyle("numberAttributes", null);
	private Style commentsAttributes = addStyle("commentsAttributes", null);
	private Style characterAttributes = addStyle("characterAttributes", null);
	private Style preprocessAttributes = addStyle("preprocessAttributes", null);

	//Liste des marqueurs de coloration
	private String startMultilineComment = "/*";
	private String endMulitilineComment = "*/";
	private String commentStart = "//";
	private String preprocessorMarker = "#";

	//Liste des mots clés a utiliser
	private Vector<String> keywords = new Vector<String>();
	
	//Choix de rmplacement des tabulations
	private boolean replaceTab = defaultReplaceTab;
	private int	tabSize = defaultTabSize;

    // Gestion pour la fonction annuler
    private UndoManager undoManager;
    
	
	/**
	 *  Constructeur de la classe
	 * 
	 */
	public CodeDocument() {

		//Définition des attributs par défauts
		StyleConstants.setBold(keywordAttributes, true);
		StyleConstants.setForeground(keywordAttributes, defaultKeywordColor);
		StyleConstants.setForeground(stringAttributes, defaultStringColor);
		StyleConstants.setForeground(numberAttributes, defaultNumberColor);
		StyleConstants.setForeground(characterAttributes, defaultCharacterColor);
		StyleConstants.setForeground(commentsAttributes, defaultCommentsColor);
		StyleConstants.setForeground(preprocessAttributes, defaultPreprocessColor);
		undoManager = new UndoManager();
		this.addUndoableEditListener(undoManager);
		undoManager.setLimit(30);
	
	}


	/**
	 * Définit les tabulations a utiliser
	 * 
	 * @param tabSet : tabulations a utiliser
	 */
	public void setTabSet(TabSet tabSet){

		StyleConstants.setTabSet(normalAttributes, tabSet);

	}

	/**
	 * Donne les tabulations (tabSet) utilisés
	 * 
	 * @return tabSet : tabSet utilisés
	 */
	public TabSet getTabSet(){

		return StyleConstants.getTabSet(normalAttributes);

	}

	/**
	 *   Scanne et met en forme le texte pour la coloration syntaxique
	 * 
	 */
	public void reScan(){
		this.removeUndoableEditListener(undoManager);
		//Point actuel du scan
		int off = 0;
		
		setCharacterAttributes(0, getLength(),normalAttributes , true);
		setParagraphAttributes(0, getLength(),normalAttributes , true);

		String val = "";
		Segment segmentCourant = new Segment();

		//Scan jusqu'à la fin du document
		do{
			try{
				getText(off++, 1, segmentCourant);
			}catch(Exception e){e.printStackTrace();}
			val = segmentCourant.toString();



			//Check du commentaire uniligne
			if (val.equals(commentStart.substring(0,1)) && off < this.getLength()){
				try{
					getText(off, 1, segmentCourant);
				}catch(Exception e){e.printStackTrace();}

				if((val + segmentCourant.toString()).startsWith(commentStart))

					off = scanComment(off -1);
			}
			
			//Check du commentaire multiligne
			if (val.equals(startMultilineComment.substring(0,1)) && off < this.getLength()){
				try{
					getText(off, 1, segmentCourant);
				}catch(Exception e){e.printStackTrace();}
				if((val + segmentCourant.toString()).startsWith(startMultilineComment))
					off = scanMultiComment(off - 1);
			}

			//Check de l'instruction préprocesseur
			if (val.startsWith(preprocessorMarker) && off < this.getLength()){


				off = scanPreprocess(off -1);
			}


			//Check d'un string
			if (val.startsWith("\"") && off < this.getLength()){

				off = scanString(off -1);
			}


			//Scan des Caractère
			if(val.startsWith("'")){

				off = scanChar(off -1);
			}


			//Scan des nombres
			if(Character.isDigit(val.charAt(0)) 
					|| Character.isDigit(val.charAt(0)))
			{
				off = scanNumber(off-1);
			}
			
			

			//Scan des nombres commencant par un point
			if (val.startsWith(".")  && off < this.getLength()){
				try{
					getText(off, 1, segmentCourant);
				}catch(Exception e){e.printStackTrace();}

				if(Character.isDigit(segmentCourant.toString().charAt(0)))

					off = scanNumber(off -1);
			}

			//Scan des mots Cle
			if(Character.isLetter(val.charAt(0)))
				off = scanKeyWord(off-1);





		}while (off < this.getLength());
		this.addUndoableEditListener(undoManager);
	}


	/**
	 * Scanne un caractère et le met en forme
	 * 
	 * @param debut emplacement du curseur de scannage pour commencer
	 * @return emplacement du curseur de scannage a la fin de la fonction
	 */
	private int scanChar (int debut){

		int off = debut;
		String val = "";
		Segment segmentCourant = new Segment();


		do{
			try{
				getText(off++, 1, segmentCourant);
			}catch(Exception e){e.printStackTrace();}
			val += segmentCourant.toString();
		}while(!(off > debut + 1 && ! val.endsWith("\\'") && val.endsWith("'")) && off < this.getLength());


		//Remplacer le string ou mettre en forme
		this.setCharacterAttributes(debut, off - debut , characterAttributes, true);


		return off;

	}

	/**
	 * Scanne un mot-clé et le met en forme
	 * 
	 * @param debut emplacement du curseur de scannage pour commencer
	 * @return emplacement du curseur de scannage a la fin de la fonction
	 */
	private int scanKeyWord (int debut){

		int off = debut;
		String val = "";
		Segment segmentCourant = new Segment();

		do{
			try{
				getText(off++, 1, segmentCourant);
			}catch(Exception e){e.printStackTrace();}
			val += segmentCourant.toString();
		}while((	!Character.isWhitespace(val.charAt(val.length()-1))
				&& (	Character.isDigit(val.charAt(val.length()-1)) 
						|| Character.isLetter(val.charAt(val.length() -1))
						|| val.substring(val.length() - 1,val.length() - 1) == "_"))
						&& off < this.getLength());
		
		if(off < this.getLength()){
			
			val = val.substring(0, val.length()-1);
			off--;
		}
		
		if (keywords.contains(val.trim().toLowerCase())){

			//Remplacer le string ou mettre en forme
			super.setCharacterAttributes(debut, off-debut, keywordAttributes, true);
		}


		return off;

	}

	
	/**
	 * Scanne un nombre entier ou décimal et le met en forme
	 * 
	 * @param debut emplacement du curseur de scannage pour commencer
	 * @return emplacement du curseur de scannage a la fin de la fonction
	 */
	private int scanNumber (int debut){

		int off = debut;
		String val = "";
		Segment segmentCourant = new Segment();

		do{
			try{
				getText(off++, 1, segmentCourant);
			}catch(Exception e){e.printStackTrace();}
			val += segmentCourant.toString();
		}while(val.endsWith(".") ||(Character.isDigit(val.charAt(val.length()-1))) && off < this.getLength());

		if(!Character.isDigit(val.charAt(val.length()-1)))
			off--;

		//Remplacer le string ou mettre en forme
		this.setCharacterAttributes(debut, off - debut, numberAttributes, true);


		return off;

	}

	
	/**
	 * Scanne un string et le met en forme
	 * 
	 * @param debut emplacement du curseur de scannage pour commencer
	 * @return emplacement du curseur de scannage a la fin de la fonction
	 */
	private int scanString (int debut){

		int off = debut;
		String val = "";
		Segment segmentCourant = new Segment();


		do{
			try{
				getText(off++, 1, segmentCourant);
			}catch(Exception e){e.printStackTrace();}
			val += segmentCourant.toString();
			
		}while(!(off > debut + 1 && val.endsWith("\"") && !val.endsWith("\\\"")) && off < this.getLength());

		//Remplacer le string ou mettre en forme
		this.setCharacterAttributes(debut, off-debut, stringAttributes, true);


		return off;

	}

	
	/**
	 * Scanne une instruction préprocesseur et la met en forme
	 * 
	 * @param debut emplacement du curseur de scannage pour commencer
	 * @return emplacement du curseur de scannage a la fin de la fonction
	 */
	private int scanPreprocess(int debut){

		int off = debut;
		String val = "";
		Segment segmentCourant = new Segment();

		do{
			try{
				getText(off++, 1, segmentCourant);
			}catch(Exception e){e.printStackTrace();}
			val += segmentCourant.toString();
		}while(!val.endsWith(startMultilineComment) && !val.endsWith(commentStart) && !val.endsWith("\n") && off < this.getLength());
		
		//on prends en compte les commentaires
		if (val.endsWith(startMultilineComment) || val.endsWith(commentStart))
			off -= 2;

		//Remplacer le string ou mettre en forme
		this.setCharacterAttributes(debut, off-debut, preprocessAttributes, true);


		return off;

	}

	/**
	 * Scanne un commentaire et le met en forme
	 * 
	 * @param debut emplacement du curseur de scannage pour commencer
	 * @return emplacement du curseur de scannage a la fin de la fonction
	 */
	private int scanComment(int debut){

		int off = debut;
		String val = "";
		Segment segmentCourant = new Segment();

		do{
			try{
				getText(off++, 1, segmentCourant);
			}catch(Exception e){e.printStackTrace();}
			val += segmentCourant.toString();
		}while(!val.endsWith("\n") && off < this.getLength());

		//Remplacer le string ou mettre en forme
		this.setCharacterAttributes(debut, off-debut, commentsAttributes, true);


		return off;

	}

	/**
	 *	scanne et colore les commentaires multilignes
	 * 
	 * @param commentsAttributes the commentsAttributes to set
	 */
	private int scanMultiComment(int debut){

		int off = debut;
		String val = "";
		Segment segmentCourant = new Segment();


		do{
			try{
				getText(off++, 1, segmentCourant);
			}catch(Exception e){e.printStackTrace();}
			val += segmentCourant.toString();
		}while(!val.endsWith(endMulitilineComment) && off < this.getLength());

		//Remplacer le string ou mettre en forme
		this.setCharacterAttributes(debut, off-debut, commentsAttributes, true);



		return off;

	}

	/**
	 * @see javax.swing.text.AbstractDocument#insertString(int, java.lang.String, javax.swing.text.AttributeSet)
	 */
	public void insertString(int offs,
			String str,
			AttributeSet a) throws BadLocationException{
		
		String replace = "";
		for (int i = 0; i <tabSize; i++)
			replace += " ";
		
		if (str == "\t" && replaceTab)
			str = replace;
		
		super.insertString(offs, str, null);

		reScan();

	}


	/**
	 * @see javax.swing.text.AbstractDocument#remove(int, int)
	 */
	public void remove(int arg0, int arg1) throws BadLocationException
	{
		super.remove(arg0, arg1);   

		reScan();
	}


	/**
	 *   Donne la liste des mots clés utilisés pour la coloration syntaxique
	 * 
	 * @return Liste des mots clés en cours d'utilisation
	 */
	public Vector getKeywords(){
		return this.keywords;
	}


	/**
	 * Définit une liste de mots clés a utiliser
	 * 
	 * @param aKeywordList liste des mots clés a utiliser
	 */
	public void setKeywords(Vector aKeywordList){
		this.keywords = aKeywordList;
	}

	/**
	 * Donne les attributs de mise en forme des commentaires
	 * 
	 * @return reference au  commentsAttributes
	 */
	public Style getCommentsAttributes() {
		return commentsAttributes;
	}


	/**
	 * Definit les attributs de mise en forme des commentaires
	 * 
	 * @param commentsAttributes the commentsAttributes to set
	 */
	public void setCommentsAttributes(Style commentsAttributes) {
		this.commentsAttributes = commentsAttributes;
	}


	/**
	 * Donne les attributs de mise en forme des mots clés
	 * 
	 * @return reference au  keywordAttributes
	 */
	public Style getKeywordAttributes() {
		return keywordAttributes;
	}


	/**
	 * Definit les attributs de mise en forme des commentaires
	 * 
	 * @param keywordAttributes the keywordAttributes to set
	 */
	public void setKeywordAttributes(Style keywordAttributes) {
		this.keywordAttributes = keywordAttributes;
	}


	/**
	 * Definit les attributs de mise en forme des nombres
	 * 
	 * @param numberAttributes the numberAttributes to set
	 */
	public void setNumberAttributes(Style numberAttributes) {
		this.numberAttributes = numberAttributes;
	}


	/**
	 * Donne les attributs de mise en forme des strings
	 * 
	 * @return reference au  stringAttributes
	 */
	public Style getStringAttributes() {
		return stringAttributes;
	}

	/**
	 * Definit les attributs de mise en forme des strings
	 * 
	 * @param stringAttributes the stringAttributes to set
	 */
	public void setStringAttributes(Style stringAttributes) {
		this.stringAttributes = stringAttributes;
	}


	/**
	 * Donne les attributs de mise en forme des bombres
	 * 
	 * @return reference au numberAttributes
	 */
	public Style getNumberAttributes() {
		return numberAttributes;
	}


	/**
	 * Etat de remplacement des tabulations par des espaces
	 * 
	 * @return vrai si remplacé faux sinon
	 */
	public boolean isReplaceTab() {
		return replaceTab;
	}


	/**
	 * Etat de remplacement des tabulations par des espaces
	 * 
	 * Attention: ne remplace pas les tabulations déjà insérées
	 * 
	 * @param replaceTab vrai si il faut remplacer faux sinon
	 */
	public void setReplaceTab(boolean replaceTab) {
		this.replaceTab = replaceTab;
	}


	/**
	 * 
	 * Donne la taille des tabulations en nombres d'espaces
	 * 
	 * @return taille des tabulations
	 */
	public int getTabSize() {
		return tabSize;
	}


	/**
	 * Definit la taille des tabulations en nombres d'espaces
	 * 
	 * @param tabSize taille des tabulations (nombre d'espaces)
	 */
	public void setTabSize(int tabSize) {
		this.tabSize = tabSize;
	}


	/**
	 * Donne les attributs de mise en forme des caractères
	 * 
	 * Exemplex de caractère: '\n'
	 * 
	 * @return the characterAttributes
	 */
	public Style getCharacterAttributes() {
		return characterAttributes;
	}


	/**
	 * Definit les attributs de mise en forme des caractères
	 * 
	 * @param characterAttributes the characterAttributes to set
	 */
	public void setCharacterAttributes(Style characterAttributes) {
		this.characterAttributes = characterAttributes;
	}
	
	
	/**
	 * Retourne le gestionnaire pour Annuler/Répéter
	 * @return Le gestionnaire pour Annuler/répéter
	 */
	public UndoManager getUndoManager() {
		return undoManager;
	}
	
	/**
	 * Donne les attributs de mise en forme des instructions préprocesseur
	 * 
	 * @return the preprocessAttributes
	 */
	public Style getPreprocessAttributes() {
		return preprocessAttributes;
	}


	/**
	 * Definit les attributs de mise en forme des instructions préprocesseur
	 * 
	 * @param preprocessAttributes the preprocessAttributes to set
	 */
	public void setPreprocessAttributes(Style preprocessAttributes) {
		this.preprocessAttributes = preprocessAttributes;
	}

	/**
	 * Donne le marqueur de début de commentaire uniligne
	 * 
	 * @return marqueur
	 */
	public String getCommentStart() {
		return commentStart;
	}

	/**
	 * Definit le marqueur de début de commentaire uniligne
	 * 
	 * @param commentStart marqueur
	 */
	public void setCommentStart(String commentStart) {
		this.commentStart = commentStart;
	}


	/**
	 * Donne le marqueur de fin de commentaire multiligne
	 * 
	 * @return marqueur
	 */
	public String getEndMulitilineComment() {
		return endMulitilineComment;
	}

	/**
	 * Définit le marqueur de fin de commentaire multiligne
	 * 
	 * @param endMulitilineComment marqueur
	 */
	public void setEndMulitilineComment(String endMulitilineComment) {
		this.endMulitilineComment = endMulitilineComment;
	}

	/**
	 * Donne le marqueur d'instruction préprocesseur
	 * 
	 * @return marqueur
	 */
	public String getPreprocessorMarker() {
		return preprocessorMarker;
	}

	/**
	 * Définit le marqueur d'instruction préprocesseur
	 * 
	 * @param preprocessorMarker marqueur
	 */
	public void setPreprocessorMarker(String preprocessorMarker) {
		this.preprocessorMarker = preprocessorMarker;
	}

	/**
	 * Donne le marqueur de début de commentaire multiligne
	 * 
	 * @return marqueur
	 */
	public String getStartMultilineComment() {
		return startMultilineComment;
	}

	/**
	 * Définit le marqueur de début de commentaire multiligne
	 * 
	 * @param startMultilineComment marqueur
	 */
	public void setStartMultilineComment(String startMultilineComment) {
		this.startMultilineComment = startMultilineComment;
	}

} 
